package com.ubb.dbms.core.operatorEvaluator;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.bson.types.ObjectId;
import org.json.simple.JSONObject;

import xml.XmlHelper;

import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.MongoClient;
import com.mongodb.WriteResult;
import com.ubb.dbms.core.Utils.SQLExceptions.IndexKeyNullException;
import com.ubb.dbms.core.Utils.SQLExceptions.ItemAlreadyExistsException;
import com.ubb.dbms.core.Utils.SQLExceptions.ItemNotExistsException;
import com.ubb.dbms.core.checker.DefaultChecker;
import com.ubb.dbms.core.checker.DomainObjectChecker;
import com.ubb.dbms.core.connection.ServerHandler;
import com.ubb.dbms.core.connection.result.MessageResult;
import com.ubb.dbms.core.fileManagers.dataFileManager.DataFileManager;
import com.ubb.dbms.core.fileManagers.dataFileManager.DataFileManagerImpl;
import com.ubb.dbms.core.fileManagers.dataFileManager.DataFileParser;
import com.ubb.dbms.core.fileManagers.indexFileManager.IndexFileIO;
import com.ubb.dbms.core.fileManagers.indexFileManager.IndexFileManager;
import com.ubb.dbms.core.fileManagers.indexFileManager.IndexFileManagerImpl;
import com.ubb.dbms.core.fileManagers.indexFileManager.bPlusTree.BPlusTree;
import com.ubb.dbms.core.fileManagers.indexFileManager.bPlusTree.Node;
import com.ubb.dbms.core.operatorEvaluator.util.JsonConverter;
import com.ubb.dbms.core.systemCatalog.SystemCatalog;
import com.ubb.dbms.core.systemCatalog.SystemCatalogImpl;
import com.ubb.dbms.domain.queryData.Condition;
import com.ubb.dbms.domain.queryData.DeleteElements;
import com.ubb.dbms.domain.queryData.SelectElements;
import com.ubb.dbms.domain.queryData.TableRowElements;
import com.ubb.dbms.domain.queryResult.RawDataConverter;
import com.ubb.dbms.domain.queryResult.SelectResult;
import com.ubb.dbms.domain.queryResult.SelectedRow;
import com.ubb.dbms.domain.xml.Database;
import com.ubb.dbms.domain.xml.IAttribute;
import com.ubb.dbms.domain.xml.IndexAttributes;
import com.ubb.dbms.domain.xml.IndexFile;
import com.ubb.dbms.domain.xml.IndexFiles;
import com.ubb.dbms.domain.xml.Table;

public class OperatorEvaluatorImpl implements OperatorEvaluator {

	private SystemCatalog systemCatalog = new SystemCatalogImpl();
	private DataFileManager dataFileManager = new DataFileManagerImpl();
	private IndexFileManager indexFileManager = new IndexFileManagerImpl();
	private DomainObjectChecker checker = new DefaultChecker(systemCatalog);

	@Override
	public void manageCreateDatabase(Database database)
			throws ItemAlreadyExistsException {
		if (checker.checkDatabase(database)) {
			systemCatalog.createDatabase(database);
			dataFileManager.createFolderForDatabase(database.getDatabaseName());
		}
	}

	@Override
	public void manageUseDatabase(String databaseName) {
		checker.checkUseDatabase(databaseName);
		// select DiscID from disciplines where DiscID = 2
//		SelectElements elements = new SelectElements();
//		elements.setTableNames(new String[] { "disciplines" });
//		elements.setProjections(new String[] { "DiscID" });
//		Condition condition = new Condition();
//		condition.setLeftOperand("DiscID");
//		condition.setRightOperator("2");
//		condition.setOperator(Operator.EQUAL);
//		elements.setCondition(new Condition[] { condition });
//		try {
//			manageSelect(elements, databaseName);
//		} catch (ItemNotExistsException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
	}

	@Override
	public void manageCreateTable(final Table table, String databaseName)
			throws ItemAlreadyExistsException {
		if (checker.checkTable(table, databaseName)) {
			IndexFiles indexFiles = new IndexFiles();
			indexFiles.setIndexFileList(new ArrayList<IndexFile>());
			
			IndexFile indexFile = new IndexFile();
			String primaryKey = table.getPrimaryKey().getPkAttribute().get(0).getValue();
			indexFile.setIndexName(primaryKey+"_"+table.getTableName());
			indexFile.setKeyLenght("4");
			indexFile.setIndexType(IndexFile.B_PLUS_TREE_INDEX_TYPE);
			indexFile.setIsUnique("1");
			IAttribute attribute = new IAttribute();
			attribute.setValue(primaryKey);
			
			IndexAttributes indexAttributes = new IndexAttributes();
			indexAttributes.setIndexAttributeList(new ArrayList<IAttribute>());
			indexAttributes.getIndexAttributeList().add(attribute);
			indexFile.setIndexAttribute(indexAttributes);
			
			indexFiles.getIndexFileList().add(indexFile);
			table.setIndexFiles(indexFiles);

			systemCatalog.createTable(table, databaseName);
			
			MongoClient mongo = ServerHandler.getMongoDB();
			DB db = mongo.getDB(databaseName);				
			db.getCollection(table.getTableName()).insert(new BasicDBObject());
			db.getCollection(primaryKey+"_"+table.getTableName()).insert(new BasicDBObject());
		}
	}

	public void manageCreateIndex(final IndexFile indexFile,
			final String tableName, final String usesDatabase)
			throws ItemNotExistsException {
		Table table = systemCatalog.getTable(tableName, usesDatabase);
		indexFile.setKeyLenght(String.valueOf(DefaultChecker.DEFAULT_INT_LENGTH));
		systemCatalog.createIndex(indexFile, table, usesDatabase);
		MongoClient mongo = ServerHandler.getMongoDB();
		DB db = mongo.getDB(usesDatabase);				
		db.getCollection(indexFile.getIndexName()).insert(new BasicDBObject());	
	}

	@Override
	public void manageInsertRow(TableRowElements rowElements, String tableName,
			String usesDatabase) throws ItemNotExistsException,
			IndexKeyNullException {
		if (checker.checkUseDatabase(usesDatabase)) {
			Table table = systemCatalog.getTable(tableName, usesDatabase);
			if (checker.checkRowElements(rowElements, table)
					&& checker.checkDuplicateKey(rowElements, table,
							usesDatabase)) {
				
				MongoClient mongo = ServerHandler.getMongoDB();
				DB db = mongo.getDB(usesDatabase);				
				BasicDBObject obj = new BasicDBObject("value", XmlHelper.writeString(rowElements.getColumnValues()));
				db.getCollection(table.getTableName()).insert(obj);
				ObjectId id = (ObjectId)obj.get( "_id" );

				for (IndexFile indexFile : table.getIndexFiles()
						.getIndexFileList()) {
					String keyColumnName = indexFile.getIndexAttribute()
							.getIndexAttributeList().get(0).getValue();
					String key = (String) rowElements.getColumnValue(keyColumnName);
					if (key != null) {
						DBCollection indFile = db.getCollection(keyColumnName+"_"+table.getTableName());
						if ("1".equals(indexFile.getIsUnique())) {
							indFile.insert(new BasicDBObject("_id",(String)rowElements.getColumnValue(keyColumnName))
														.append("value", id));
						} else {
							indFile.insert(new BasicDBObject("_id",(String)rowElements.getColumnValue(keyColumnName)+"#"+id));
						}						
					} else {
						throw new IndexKeyNullException(keyColumnName);
					}
				}
				/*int dataFileRowNr = dataFileManager.insertRow(rowElements,
						table, usesDatabase);
				for (IndexFile indexFile : table.getIndexFiles()
						.getIndexFileList()) {
					String keyColumnName = indexFile.getIndexAttribute()
							.getIndexAttributeList().get(0).getValue();
					Object key = rowElements.getColumnValue(keyColumnName);
					if (key != null) {
						indexFileManager.insertKey(key, dataFileRowNr,
								usesDatabase, table, indexFile);
					} else {
						throw new IndexKeyNullException(keyColumnName);
					}
				}*/
			}
		}
	}
	
	@Override
	public void manageDelete(DeleteElements deleteElements, String usesDatabase) throws ItemNotExistsException {
		Table table = systemCatalog.getTable(deleteElements.getTableName(), usesDatabase);
		List<IndexFile> indexFiles = table.getIndexFiles().getIndexFileList();

		if (deleteElements.getCondition() == null || deleteElements.getCondition().length <= 1) {
			String conditionLeftOperand = deleteElements.getCondition() == null ? null : deleteElements.getCondition()[0].getLeftOperand().toString();
			String conditionRightOperand = deleteElements.getCondition() == null ? null : deleteElements.getCondition()[0].getRightOperand().toString();
			IndexFile indexFile = getIndexFile(indexFiles,
					conditionLeftOperand);
			if (indexFile == null) {
				return;
			} else {
				MongoClient mongo = ServerHandler.getMongoDB();
				DB db = mongo.getDB(usesDatabase);				
				
				if ("1".equals(indexFile.getIsUnique())) {
					BasicDBObject whereQuery = new BasicDBObject();
					whereQuery.put("_id", conditionRightOperand);
					DBCursor cursor = db.getCollection(indexFile.getIndexName()).find(whereQuery);
					
					while (cursor.hasNext()) {
						ObjectId id  = (ObjectId)cursor.next().get("value");
						
						BasicDBObject delete = new BasicDBObject();
						delete.put("_id", id);
						db.getCollection(table.getTableName()).remove(delete);
					}
					
					db.getCollection(indexFile.getIndexName()).remove(whereQuery);					
				} else {
					
				}
			}
		} else {
			// SelectedRow SelectedRowConver(table, byte[],
			// selectElements.getProjections());
		}		
	}

	@Override
	public SelectResult manageSelect(SelectElements selectElements,
			String usesDatabase) throws ItemNotExistsException {
		Table table = systemCatalog.getTable(selectElements.getTableNames()[0],
				usesDatabase);
		List<IndexFile> indexFiles = table.getIndexFiles().getIndexFileList();

		SelectResult selectResult = new SelectResult();
		if (selectElements.getCondition() == null || selectElements.getCondition().length <= 1) {
			String conditionLeftOperand = selectElements.getCondition() == null ? null : selectElements.getCondition()[0].getLeftOperand().toString();
			IndexFile indexFile = getIndexFile(indexFiles,
					conditionLeftOperand);
			if (indexFile == null) {
				selectResult = getDataWithConditionWithProjection(
						table, usesDatabase, selectElements.getCondition() == null ? null : selectElements.getCondition()[0],
						selectElements.getProjections());
			} else {
				selectResult = getDataUsingIndexFileWithConditionWithProjection(
						indexFile, table, usesDatabase,
						selectElements.getCondition()[0],
						selectElements.getProjections());
			}
		} else {
			// SelectedRow SelectedRowConver(table, byte[],
			// selectElements.getProjections());
		}
		// megkeressuk a felhasznalt indexet
		// kivalasztjuk a legjobbak (==)
		// Vagy BPluss fat vagy indexFile attributumot es egy conditiont!!!
		// TODO ... = letrehozok egy b+tree obj es utana meghivom ra a keresest
		// >
		JSONObject json = JsonConverter.getJsonFromSelectResult(selectResult);
		MessageResult.setMessage(json);
		return null;
	}

	private SelectResult getDataUsingIndexFileWithConditionWithProjection(
			IndexFile indexFile, Table table, String usesDatabase,
			Condition condition, String[] projections) {
		// projection = [id, name] when Select id, name or *

		Map<String, String> projectionsMap= RawDataConverter.projectionArrayToMap(projections);
		// String keyColumnName = "";
		String indexFileFolder = "";
		String indexFilePath = "";
		String rsIndexFilePath = "";
		// String primaryKeyColumn =
		// table.getPrimaryKey().getPkAttribute().get(0).getValue();

		indexFileFolder = IndexFileManagerImpl.getIndexFileFolder(usesDatabase,
				table.getTableName());
		rsIndexFilePath = IndexFileManagerImpl.getRsIndexFilePath(
				indexFileFolder, indexFile);

		IndexFileIO indexFileIO = new IndexFileIO(indexFilePath,
				rsIndexFilePath, IndexFileManager.HEADER_LENGTH,
				IndexFileManagerImpl.getIndexRowLength());
		int rootNodeRowNr = indexFileIO.readFileHeader();
		BPlusTree bPlusTree;
		if (rootNodeRowNr == 0) {
			bPlusTree = new BPlusTree(indexFileIO);
		} else {
			Node rootNode = indexFileIO.readRow(rootNodeRowNr);
			bPlusTree = new BPlusTree(rootNode, indexFileIO);
		}

		int right = Integer.parseInt(condition.getRightOperand().toString());
		Node selectResultNode = bPlusTree.getLeafNodeForKey(right);
		//Node selectResultNode = bPlusTree.searchForNode(right);
		boolean first = true;
		SelectResult result = new SelectResult();
		while (first || (selectResultNode != null
				&& selectResultNode.getmNumKey(0) == right)) {
			first = false;
			int i = 0;
			
			while (selectResultNode.getmNumKey(i) != right) {
				i++;
			}
			while (selectResultNode.getmNumKey(i) == right && i < IndexFileManager.T * 2) {
				//megkeresni a datafileba a nodehoz tartozo adatokat
				int dataFilePointer = Integer.parseInt(selectResultNode.getmObject(i).toString());
				SelectedRow row = dataFileManager.readRow(table, usesDatabase,dataFilePointer, projectionsMap);
				result.addSelectedRow(row);
				i++; 
			}
			selectResultNode = selectResultNode.getmNextNode();
		}

		return result;
	}

	private SelectResult getDataWithConditionWithProjection(Table table,
			String usesDatabase, Condition condition, String[] projections) {
		
		Map<String, String> projectionsMap = RawDataConverter.projectionArrayToMap(projections);
		DataFileParser dataFileParser = new DataFileParser(table, usesDatabase, projectionsMap);
		SelectResult selectResult = new SelectResult();
		
		//TODO checker again
		if (condition != null && condition.getRightOperand() instanceof String) {
			condition.setRightOperand(condition.getRightOperand().toString().replaceAll("\'$|^\'", ""));
		}
		
		while (dataFileParser.hasNext()) {
			SelectedRow selectedRow = dataFileParser.next();
			if (condition == null || selectedRow.checkCondition(condition)) {
				selectResult.addSelectedRow(selectedRow);
			}
		}
		
		return selectResult;
	}
	
	private SelectResult getDataWithCondition(Table table,
			String usesDatabase, Condition condition) {
		
		DataFileParser dataFileParser = new DataFileParser(table, usesDatabase);
		SelectResult selectResult = new SelectResult();
		
		//TODO checker again
		if (condition != null && condition.getRightOperand() instanceof String) {
			condition.setRightOperand(condition.getRightOperand().toString().replaceAll("\'$|^\'", ""));
		}
		
		while (dataFileParser.hasNext()) {
			SelectedRow selectedRow = dataFileParser.next();
			if (condition == null || selectedRow.checkCondition(condition)) {
				selectResult.addSelectedRow(selectedRow);
			}
		}
		
		return selectResult;
	}

	private IndexFile getIndexFile(List<IndexFile> indexFiles,
			String leftOperand) {
		
		if (leftOperand == null) {
			return null;
		}

		for (IndexFile indexFile : indexFiles) {
			String indexColumnName = indexFile.getIndexAttribute()
					.getIndexAttributeList().get(0).getValue();
			if (leftOperand.equalsIgnoreCase(indexColumnName)) {
				return indexFile;
			}
		}
		return null;
	}

	@Override
	public void manageDropDatabase(String databasename) {
		Database database = new Database();
		database.setDatabaseName(databasename);
		if (!databasename.trim().isEmpty() && !checker.checkDatabase(database)) {
			systemCatalog.dropDatabase(databasename);
		}
	}

}
